﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.ServiceModel;
using System.ServiceModel.Channels;
using Microsoft.Practices.Unity;
using Microsoft.Practices.ObjectBuilder2;

namespace WDA.Core
{
	/// <summary>
	/// Describes remote service instance using address (both service and it's monitor) and communication 
	/// protocol (both service and it's monitor).	
	/// </summary>
   public sealed class RemoteServiceDescription
   {
		/// <summary>
		/// Gets or sets service's communication protocol binding.
		/// </summary>
      public Binding ServiceBinding { get; set; }
		/// <summary>
		/// Gets or sets service address.
		/// </summary>
      public EndpointAddress ServiceAddress { get; set; }
		/// <summary>
		/// Gets or sets monitor's communication protocol binding.
		/// </summary>
      public Binding MonitorBinding { get; set; }
		/// <summary>
		/// Gets or sets monitor address.
		/// </summary>
      public EndpointAddress MonitorAddress { get; set; }
   }

	/// <summary>
	/// Base class for controllers of remote services. It delegates responsibility for determining frequency
	/// of invoking load-blanancig system to derived classes.
	/// </summary>
   public abstract class RemoteServiceController : IServiceController, IBuilderAware
   {
      #region Members
      private readonly bool _singleThreaded;
      private readonly Type _serviceType;
      private readonly Type _monitorType;
      private RemoteServiceHost _bestHost;
      private readonly Dictionary<Guid, RemoteServiceHost> _hosts = new Dictionary<Guid, RemoteServiceHost>();
      private readonly object _synchRoot = new object();
      private bool _disposed;
      private IEnumerable<RemoteServiceDescription> _remoteServices;
      #endregion

      #region Dependency Proerties
		/// <summary>
		/// Main rgeneral-purpose untime-level container.
		/// </summary>
      [Dependency]
      public IUnityContainer Container { get; set; }
      #endregion

      #region Constructor
		/// <summary>
		/// Creates new instance of <see cref="RemoteServiceController"/>.
		/// </summary>
		/// <param name="serviceType">CLR type of service contract.</param>
		/// <param name="monitorType">CLR type of monitor contract.</param>
      /// <param name="singleThreaded">Does runtime run in single-treaded mode?</param>
		/// <param name="remoteServices">Collection of objects describing remote service instances.</param>
      protected RemoteServiceController(Type serviceType, Type monitorType, bool singleThreaded, IEnumerable<RemoteServiceDescription> remoteServices)
      {
         _serviceType = serviceType;
         _monitorType = monitorType;
         _remoteServices = remoteServices;
         _singleThreaded = singleThreaded;
      }

      #region IBuilderAware Members
		/// <summary>
		/// Generates unique id for evenry instance of service and creates an object for accessing it.		
		/// </summary>
		/// <param name="buildKey"></param>
      public void OnBuiltUp(object buildKey)
      {
         foreach (RemoteServiceDescription descr in _remoteServices)
         {
            Guid key = Guid.NewGuid();
            _hosts[key] = Container.BuildUp(new RemoteServiceHost(_serviceType, _monitorType, _singleThreaded, descr.ServiceBinding, descr.ServiceAddress, descr.MonitorBinding, descr.MonitorAddress));
         }
         _remoteServices = null;
      }
      public void OnTearingDown()
      {
         foreach (RemoteServiceHost host in _hosts.Values)
         {
            Container.Teardown(host);
         }
      }
      #endregion

      #endregion

      #region Protected interface
		/// <summary>
		/// Checks if load-balancing engine should be invoked to determine optimal service provided for this
		/// service.		
		/// </summary>
		/// <returns>True, if load-balancing engine should be invoked now.</returns>
      protected abstract bool TestRequiresBestHostReassesment();
      #endregion

      #region Internal interface
		/// <summary>
		/// Creates proxy object for accessing optimal (chosen by load-balancing engine) service instance.
		/// If it is desired, it internally run load-balancing engine to determine optimal provider.		
		/// </summary>
		/// <returns>A proxy to the service.</returns>
      public object CreateServiceChannel()
      {
         lock (_synchRoot)
         {
            if (_bestHost == null || TestRequiresBestHostReassesment())
            {
               ILoadBalancer balancer = Container.Resolve<ILoadBalancer>();
               RemoteServiceHost bestHost = _hosts[balancer.ChooseBestServiceHost(_hosts.Select(
                    source => new KeyValuePair<Guid, object>(source.Key, source.Value.CreateServiceMonitorChannel())))];
               _bestHost = bestHost;
            }
            return _bestHost.CreateServiceChannel();
         }
      }
      #endregion

      #region Utility class
		/// <summary>
		/// Provides access to remote service instance and it's monitor.
		/// </summary>
      public sealed class RemoteServiceHost : IDisposable
      {
         private readonly bool _singleThreaded;         
         private readonly IChannelFactoryWrapper _factoryWrapper;
         private readonly IChannelFactoryWrapper _monitorFactoryWrapper;
         private bool _disposed;
         private object _cachedProxy;

			/// <summary>
			/// Stub container.
			/// </summary>
         [Dependency(WDAContainerNames.StubContainerName)]
         public IUnityContainer StubContainer { get; set; }

			/// <summary>
			/// Creates new instance of <see cref="RemoteServiceHost"/>.
			/// </summary>
			/// <param name="serviceType">CLR type of service contract.</param>
			/// <param name="monitorType">CLR type of monitor contract.</param>
         /// <param name="singleThreaded">Does runtime run in single-treaded mode (can cache proxies)?</param>
			/// <param name="binding">Service's communication protocol binding.</param>
			/// <param name="address">Service address.</param>
			/// <param name="monitorBinding">Monitor's communication protocol binding.</param>
			/// <param name="monitorAddress">Monitor address.</param>
         public RemoteServiceHost(Type serviceType, Type monitorType, bool singleThreaded,
             Binding binding, EndpointAddress address,
             Binding monitorBinding, EndpointAddress monitorAddress)
         {            
            _singleThreaded = singleThreaded;
            Type channelFatoryWrapperType = typeof(ChannelFactoryWrapper<>).MakeGenericType(serviceType);
            _factoryWrapper = (IChannelFactoryWrapper)Activator.CreateInstance(channelFatoryWrapperType, binding, address);

            Type monitorChannelFatoryWrapperType = typeof(ChannelFactoryWrapper<>).MakeGenericType(monitorType);
            _monitorFactoryWrapper = (IChannelFactoryWrapper)Activator.CreateInstance(monitorChannelFatoryWrapperType, monitorBinding, monitorAddress);
         }
			/// <summary>
			/// Returns proxy object for the service. This object internally establishes communication channel
			/// to service instance and forwards method calls to it.
			/// Proxy is built by Stub Container where all required dependencies are injected and interceptors
			/// are attached.			
			/// </summary>
			/// <remarks>
			/// In single-threaded mode this method saves creates proxy in cache and all subsequent invocations
			/// return the same cached instance.
			/// </remarks>
			/// <returns>A proxy object.</returns>
         public object CreateServiceChannel()
         {
			   object result;
            if (_singleThreaded)
            {
               if (_cachedProxy == null)
               {
                  result = _factoryWrapper.CreateChannel();
                  _cachedProxy = StubContainer.BuildUp(result.GetType(), result);                  
               }
               return _cachedProxy;
            }
			   result = _factoryWrapper.CreateChannel();
			   return StubContainer.BuildUp(result.GetType(), result);
         }
			/// <summary>
         /// Returns proxy object for the monitor. This object internally establishes communication channel
         /// to service's monitor instance and forwards method calls to it.
         /// Proxy is built by Stub Container where all required dependencies are injected and interceptors
         /// are attached.
			/// </summary>
			/// <returns></returns>
         public object CreateServiceMonitorChannel()
         {
            object result = _monitorFactoryWrapper.CreateChannel();
            return StubContainer.BuildUp(result.GetType(), result);
         }

			/// <summary>
			/// An interface for wrapping <see cref="IChannelFactory"/> to use <see cref="IChannelFactory{TChannel}.CreateChannel(EndpointAddress)"/>
			/// without need to provide generic argument.
			/// </summary>
         private interface IChannelFactoryWrapper : IDisposable
         {
            /// <summary>
            /// Forwards call to generic <see cref="IChannelFactory{TChannel}.CreateChannel(EndpointAddress)"/>
            /// </summary>
            /// <returns></returns>
            object CreateChannel();
         }
			/// <summary>
			/// Generic implementation of <see cref="IChannelFactoryWrapper"/> for wrapping 
			/// <see cref="ChannelFactory{TChannel}"/> parametrised with the same generic type.
			/// </summary>
			/// <typeparam name="TService">Type argument for <see cref="ChannelFactory{TChannel}"/> - CLR type of service contract.</typeparam>
         private sealed class ChannelFactoryWrapper<TService> : IChannelFactoryWrapper
         {
            private readonly ChannelFactory<TService> _realFactory;
            private bool _disposed;

				/// <summary>
				/// Creates new instance of <see cref="ChannelFactoryWrapper{TService}"/>.
				/// </summary>
				/// <param name="binding">Communication protocol binding.</param>
				/// <param name="remoteAddress">Address.</param>
            public ChannelFactoryWrapper(Binding binding, EndpointAddress remoteAddress)
            {
               _realFactory = new ChannelFactory<TService>(binding, remoteAddress);
               _realFactory.Open();
            }
				
            public object CreateChannel()
            {
               return _realFactory.CreateChannel();
            }

            #region IDisposable Members
				/// <summary>
				/// Closes internal <see cref="IChannelFactory{TChannel}"/> instance.
				/// </summary>
            public void Dispose()
            {
               if (!_disposed)
               {
                  _realFactory.Close();
                  _disposed = true;
               }
               GC.SuppressFinalize(this);
            }
            #endregion
         }

         #region IDisposable Members
			/// <summary>
			/// Releases contained factory wrapper objects for services and monitors. Also closes cached
			/// proxy objects if operating in single-threaded mode.		
			/// </summary>
         public void Dispose()
         {
            if (!_disposed)
            {
               _factoryWrapper.Dispose();
               _monitorFactoryWrapper.Dispose();
               if (_cachedProxy != null)
               {
                  IChannel channel = (IChannel)_cachedProxy;
                  if (channel.State == CommunicationState.Opened)
                  {
                     channel.Close();
                  }
               }
               _disposed = true;
            }
            GC.SuppressFinalize(this);
         }
         #endregion
      }
      #endregion

      #region IDisposable Members
		/// <summary>
		/// Releases all contained service access objects (<see cref="RemoteServiceHost"/>).
		/// </summary>
      public void Dispose()
      {
         if (!_disposed)
         {
            Dispose(true);
            foreach (RemoteServiceHost host in _hosts.Values)
            {
               host.Dispose();
            }
            _disposed = true;
         }
         GC.SuppressFinalize(this);
      }
      protected virtual void Dispose(bool disposing)
      {
      }
      #endregion      
   }
}
